/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.internal.logging;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.BreakIterator;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Handler;
import java.util.logging.Level;

import org.apache.geode.i18n.LogWriterI18n;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.process.StartupStatusListener;
import org.apache.geode.i18n.StringId;

/**
  * Abstract implementation of {@link InternalLogWriter}.
  * Each logger has a level and it will only print messages whose
  * level is greater than or equal to the logger's level. The supported
  * logger level constants, in ascending order, are:
  * <ol>
  * <li> {@link #ALL_LEVEL}
  * <li> {@link #FINEST_LEVEL}
  * <li> {@link #FINER_LEVEL}
  * <li> {@link #FINE_LEVEL}
  * <li> {@link #CONFIG_LEVEL}
  * <li> {@link #INFO_LEVEL}
  * <li> {@link #WARNING_LEVEL}
  * <li> {@link #ERROR_LEVEL}
  * <li> {@link #SEVERE_LEVEL}
  * <li> {@link #NONE_LEVEL}
  * </ol>
  * <p>
  * Subclasses must implement:
  * <ol>
  * <li> {@link #getLogWriterLevel}
  * <li> {@link #put(int, String, Throwable)}
  * <li> {@link #put(int, StringId, Object[], Throwable)}
  * </ol>
  */
public abstract class LogWriterImpl implements InternalLogWriter {

  // Constants
    /**
     * A bit mask to remove any potential flags added to the msgLevel.
     * Intended to be used in {@link #getRealLogLevel}.
     */
    private final static int LOGGING_FLAGS_MASK = 0x00FFFFFF;
    
    /**
     * A flag to indicate the {@link SecurityLogWriter#SECURITY_PREFIX} 
     * should be appended to the log level.
     */
    protected final static int SECURITY_LOGGING_FLAG = 0x40000000;

  static {
      Assert.assertTrue( ALL_LEVEL == Level.ALL.intValue() );
      Assert.assertTrue( NONE_LEVEL == Level.OFF.intValue() );
      Assert.assertTrue( FINEST_LEVEL == Level.FINEST.intValue() );
      Assert.assertTrue( FINER_LEVEL == Level.FINER.intValue() );
      Assert.assertTrue( FINE_LEVEL == Level.FINE.intValue() );
      Assert.assertTrue( CONFIG_LEVEL == Level.CONFIG.intValue() );
      Assert.assertTrue( INFO_LEVEL == Level.INFO.intValue() );
      Assert.assertTrue( WARNING_LEVEL == Level.WARNING.intValue() );
      Assert.assertTrue( SEVERE_LEVEL == Level.SEVERE.intValue() );
      int logLevels = FINEST_LEVEL | 
                      FINER_LEVEL  | 
                      FINE_LEVEL   |
                      CONFIG_LEVEL |
                      INFO_LEVEL   |
                      WARNING_LEVEL|
                      SEVERE_LEVEL;
      Assert.assertTrue( logLevels == (logLevels & LOGGING_FLAGS_MASK) );
      Assert.assertTrue( 0 == (logLevels & SECURITY_LOGGING_FLAG) );
  }

    /**
     * A listener which can be registered to be informed of startup events
     */
    private static volatile StartupStatusListener startupListener;

    // Constructors
    protected LogWriterImpl() {
      this.timeFormatter = DateFormatter.createDateFormat();
    }

    /**
     * Gets the writer's level.
     */
    public abstract int getLogWriterLevel();
    
    public boolean isSecure() {
      return false;
    }
    
    public static String allowedLogLevels() {
      StringBuffer b = new StringBuffer(64);
      for (int i=0; i < levelNames.length; i++) {
        if (i != 0) {
          b.append('|');
        }
        b.append(levelNames[i]);
      }
      return b.toString();
    }

    /**
     * Gets the string representation for the given <code>level</code> int code.
     */
    public static String levelToString(int level) {
      switch (level) {
      case ALL_LEVEL: return "all";
      case FINEST_LEVEL: return "finest";
      case FINER_LEVEL: return "finer";
      case FINE_LEVEL: return "fine";
      case CONFIG_LEVEL: return "config";
      case INFO_LEVEL: return "info";
      case WARNING_LEVEL: return "warning";
      case ERROR_LEVEL: return "error";
      case SEVERE_LEVEL: return "severe";
      case NONE_LEVEL: return "none";
      default: return levelToStringSpecialCase(level);
      }
    }
    /**
     * Handles the special cases for {@link #levelToString(int)} including
     * such cases as the SECURITY_LOGGING_PREFIX and an invalid log level.
     */
    private static String levelToStringSpecialCase(int levelWithFlags) {
      if((levelWithFlags & SECURITY_LOGGING_FLAG) != 0) {
        //We know the flag is set so XOR will zero it out.
        int level = levelWithFlags ^ SECURITY_LOGGING_FLAG;
        return SecurityLogWriter.SECURITY_PREFIX + levelToString(level);
      } else {
        //Needed to prevent infinite recursion
        //This signifies an unknown log level was used
        return "level-" + String.valueOf(levelWithFlags);
      }      
    }

    protected static int getRealLogLevel(int levelWithFlags) {
      if(levelWithFlags == NONE_LEVEL) {
        return levelWithFlags;
      }
      return levelWithFlags & LOGGING_FLAGS_MASK;
    }
    
    public static String join(Object[] a) {
      return join(a, " ");
    }
    public static String join(Object[] a, String joinString) {
      return join(Arrays.asList(a), joinString);
    }
    public static String join(List l) {
      return join(l, " ");
    }
    public static String join(List l, String joinString) {
      StringBuffer result = new StringBuffer(80);
      boolean firstTime = true;
      Iterator it = l.iterator();
      while (it.hasNext()) {
        if (firstTime) {
          firstTime = false;
        } else {
          result.append(joinString);
        }
        result.append(it.next());
      }
      return result.toString();
    }

    /**
     * Gets the level code for the given <code>levelName</code>.
     * @throws IllegalArgumentException if an unknown level name is given.
     */
    public static int levelNameToCode(String levelName) {
        if ("all".equalsIgnoreCase(levelName)) {
          return ALL_LEVEL;
        }
        if ("finest".equalsIgnoreCase(levelName) || "trace".equalsIgnoreCase(levelName)) {
          return FINEST_LEVEL;
        }
        if ("finer".equalsIgnoreCase(levelName)) {
          return FINER_LEVEL;
        }
        if ("fine".equalsIgnoreCase(levelName) || "debug".equalsIgnoreCase(levelName)) {
          return FINE_LEVEL;
        }
        if ("config".equalsIgnoreCase(levelName)) {
          return CONFIG_LEVEL;
        }
        if ("info".equalsIgnoreCase(levelName)) {
          return INFO_LEVEL;
        }
        if ("warning".equalsIgnoreCase(levelName) || "warn".equalsIgnoreCase(levelName)) {
          return WARNING_LEVEL;
        }
        if ("error".equalsIgnoreCase(levelName)) {
          return ERROR_LEVEL;
        }
        if ("severe".equalsIgnoreCase(levelName) || "fatal".equalsIgnoreCase(levelName)) {
          return SEVERE_LEVEL;
        }
        if ("none".equalsIgnoreCase(levelName)) {
          return NONE_LEVEL;
        }
        try {
          if (levelName.startsWith("level-")) {
            String levelValue = levelName.substring("level-".length());
            return Integer.parseInt(levelValue);
          }
        } catch (NumberFormatException ignore) {
        } catch (NullPointerException ignore) {
        }
  throw new IllegalArgumentException("Unknown log-level \"" + levelName
                                           + "\". Valid levels are: "
                                           + join(levelNames)
                                           + ".");
    }

    /**
     * Gets a String representation of the current time.
     * @return a String representation of the current time.
     */
    protected String getTimeStamp() {
      return formatDate(new Date());
    }
    /**
     * Convert a Date to a timestamp String.
     * @param d a Date to format as a timestamp String.
     * @return a String representation of the current time.
     */
    protected String formatDate(Date d) {
      try {
        synchronized (timeFormatter) {
          // Need sync: see bug 21858
          return timeFormatter.format(d);
        }
      } catch (Exception e1) {
        // Fix bug 21857
        try {
          return d.toString();
        } catch (Exception e2) {
          try {
            return Long.toString(d.getTime());
          } catch (Exception e3) {
            return "timestampFormatFailed";
          }
        }
      }
    }

    // Implementation of LogWriterI18n interface
    /**
     * Returns true if "severe" log messages are enabled.
     * Returns false if "severe" log messages are disabled.
     */
    public boolean severeEnabled() {
      return getLogWriterLevel() <= SEVERE_LEVEL;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     */
    public void severe(String msg, Throwable ex) {
      if (this.severeEnabled()) {
        this.put(SEVERE_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "severe".
     */
    public void severe(String msg) {
      this.severe(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "severe".
     */
    public void severe(Throwable ex) {
      this.severe(LocalizedStrings.EMPTY, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID, Object[] params, Throwable ex) {
      if (this.severeEnabled()) {
        this.put(SEVERE_LEVEL, msgID, params, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID, Object param, Throwable ex) {
      if (this.severeEnabled()) {
        this.put(SEVERE_LEVEL, msgID, new Object[] {param}, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID, Throwable ex) {
      severe(msgID, null, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID, Object[] params) {
      severe(msgID, params, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID, Object param) {
      severe(msgID, param, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "severe".
     * @since GemFire 6.0
     */
    public void severe(StringId msgID) {
      severe(msgID, null, null);
    }
    /**
     * @return true if "error" log messages are enabled.
     */
    public boolean errorEnabled() {
      return getLogWriterLevel() <= ERROR_LEVEL;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     */
    public void error(String msg, Throwable ex) {
      if (this.errorEnabled()) {
        this.put(ERROR_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "error".
     */
    public void error(String msg) {
      this.error(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "error".
     */
    public void error(Throwable ex) {
      this.error(LocalizedStrings.EMPTY, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID, Object[] params, Throwable ex) {
      if (this.errorEnabled()) {
        this.put(ERROR_LEVEL, msgID, params, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID, Object param, Throwable ex) {
      if (this.errorEnabled()) {
        this.put(ERROR_LEVEL, msgID, new Object[] {param}, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID, Throwable ex) {
      error(msgID, null, ex); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID, Object[] params) {
      error(msgID, params, null); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID, Object param) {
      error(msgID, param, null); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "error".
     * @since GemFire 6.0
     */
    public void error(StringId msgID) {
      error(msgID, null, null); 
    }
    /**
     * @return true if "warning" log messages are enabled.
     */
    public boolean warningEnabled() {
      return getLogWriterLevel() <= WARNING_LEVEL;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     */
    public void warning(String msg, Throwable ex) {
      if (this.warningEnabled()) {
        this.put(WARNING_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "warning".
     */
    public void warning(String msg) {
      this.warning(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "warning".
     */
    public void warning(Throwable ex) {
      this.warning(LocalizedStrings.EMPTY, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID, Object[] params, Throwable ex) {
      if (this.warningEnabled()) {
        this.put(WARNING_LEVEL, msgID, params, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID, Object param, Throwable ex) {
      if (this.warningEnabled()) {
        this.put(WARNING_LEVEL, msgID, new Object[] {param}, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID, Throwable ex) {
      warning(msgID, null, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID, Object[] params) {
      warning(msgID, params, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID, Object param) {
      warning(msgID, param, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "warning".
     * @since GemFire 6.0
     */
    public void warning(StringId msgID) {
      warning(msgID, null, null);
    }
    /**
     * @return true if "info" log messages are enabled.
     */
    public boolean infoEnabled() {
      return getLogWriterLevel() <= INFO_LEVEL
          /* (bug 29581) && !SmHelper._memorySpaceLow() */;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "information".
     */
    public void info(String msg, Throwable ex) {
      if (this.infoEnabled()) {
        this.put(INFO_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "information".
     */
    public void info(String msg) {
      this.info(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "information".
     */
    public void info(Throwable ex) {
      this.info(LocalizedStrings.EMPTY, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID, Object[] params, Throwable ex) {
      if (this.infoEnabled()) {
        this.put(INFO_LEVEL, msgID, params, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID, Object param, Throwable ex) {
      if (this.infoEnabled()) {
        this.put(INFO_LEVEL, msgID, new Object[] {param}, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID, Throwable ex) {
      info(msgID, null, ex); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID, Object[] params) {
      info(msgID, params, null); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID, Object param) {
      info(msgID, param, null); 
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "info".
     * @since GemFire 6.0
     */
    public void info(StringId msgID) {
      info(msgID, null, null); 
    }
    /**
     * @return true if "config" log messages are enabled.
     */
    public boolean configEnabled() {
      return getLogWriterLevel() <= CONFIG_LEVEL;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     */
    public void config(String msg, Throwable ex) {
      if (this.configEnabled()) {
        this.put(CONFIG_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "config".
     */
    public void config(String msg) {
      this.config(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "config".
     */
    public void config(Throwable ex) {
      this.config(LocalizedStrings.EMPTY, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID, Object[] params, Throwable ex) {
      if (this.configEnabled()) {
        this.put(CONFIG_LEVEL, msgID, params, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID, Object param, Throwable ex) {
      if (this.configEnabled()) {
        this.put(CONFIG_LEVEL, msgID, new Object[] {param}, ex);
      }
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID, Throwable ex) {
      config(msgID, null, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID, Object[] params) {
      config(msgID, params, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID, Object param) {
      config(msgID, param, null);
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "config".
     * @since GemFire 6.0
     */
    public void config(StringId msgID) {
      config(msgID, null, null);
    }
    /**
     * @return true if "fine" log messages are enabled.
     */
    public boolean fineEnabled() {
      return getLogWriterLevel() <= FINE_LEVEL
      /* (bug 29581) && !SmHelper._memorySpaceLow() */;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "fine".
     */
    public void fine(String msg, Throwable ex) {
      if (this.fineEnabled()) {
        this.put(FINE_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "fine".
     */
    public void fine(String msg) {
      this.fine(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "fine".
     */
    public void fine(Throwable ex) {
      this.fine(null, ex);
    }
    /**
     * Returns true if "finer" log messages are enabled.
     * Returns false if "finer" log messages are disabled.
     */
    public boolean finerEnabled() {
      return getLogWriterLevel() <= FINER_LEVEL
      /* (bug 29581) && !SmHelper._memorySpaceLow() */;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "finer".
     */
    public void finer(String msg, Throwable ex) {
      if (this.finerEnabled()) {
        this.put(FINER_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "finer".
     */
    public void finer(String msg) {
      this.finer(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "finer".
     */
    public void finer(Throwable ex) {
      this.finer(null, ex);
    }
    /**
     * Log a method entry.
     * <p>The logging is done using the <code>finer</code> level.
     * The string message will start with <code>"ENTRY"</code> and
     * include the class and method names.
     * @param sourceClass Name of class that issued the logging request.
     * @param sourceMethod Name of the method that issued the logging request.
     */
    public void entering(String sourceClass, String sourceMethod) {
      if (this.finerEnabled()) {
        this.finer("ENTRY " + sourceClass + ":" + sourceMethod);
      }
    }
    /**
     * Log a method return.
     * <p>The logging is done using the <code>finer</code> level.
     * The string message will start with <code>"RETURN"</code> and
     * include the class and method names.
     * @param sourceClass Name of class that issued the logging request.
     * @param sourceMethod Name of the method that issued the logging request.
     */
    public void exiting(String sourceClass, String sourceMethod) {
      if (this.finerEnabled()) {
        this.finer("RETURN " + sourceClass + ":" + sourceMethod);
      }
    }
    /**
     * Log throwing an exception.
     * <p> Use to log that a method is
     * terminating by throwing an exception. The logging is done using
     * the <code>finer</code> level.
     * <p> This is a convenience method that could be done
     * instead by calling {@link #finer(String, Throwable)}.
     * The string message will start with <code>"THROW"</code> and
     * include the class and method names.
     * @param sourceClass Name of class that issued the logging request.
     * @param sourceMethod Name of the method that issued the logging request.
     * @param thrown The Throwable that is being thrown.
     */
    public void throwing(String sourceClass, String sourceMethod,
       Throwable thrown) {
      if (this.finerEnabled()) {
        this.finer("THROW " + sourceClass + ":" + sourceMethod, thrown);
      }
    }
    /**
     * Returns true if "finest" log messages are enabled.
     * Returns false if "finest" log messages are disabled.
     */
    public boolean finestEnabled() {
      return getLogWriterLevel() <= FINEST_LEVEL
      /* (bug 29581) && !SmHelper._memorySpaceLow() */;
    }
    /**
     * Writes both a message and exception to this writer.
     * The message level is "finest".
     */
    public void finest(String msg, Throwable ex) {
      if (this.finestEnabled()) {
        this.put(FINEST_LEVEL, msg, ex);
      }
    }
    /**
     * Writes a message to this writer.
     * The message level is "finest".
     */
    public void finest(String msg) {
      this.finest(msg, null);
    }
    /**
     * Writes an exception to this writer.
     * The exception level is "finest".
     */
    public void finest(Throwable ex) {
      this.finest(null, ex);
    }
    /**
     * Writes both a message and exception to this writer.
     * If a startup listener is registered,
     * the message will be written to the listener as well
     * to be reported to a user.
     * @since GemFire 7.0
     */
    public void startup(StringId msgID, Object[] params) {
      String message = msgID.toLocalizedString(params);
      
      StartupStatusListener listener = startupListener;
      if(listener != null) {
        listener.setStatus(message);
      }
      
      if (this.infoEnabled()) {
        this.put(INFO_LEVEL, message, null);
      }
    }
    // internal implemenation methods
    /**
     * Logs a message and an exception of the given level.
     * 
     * @param msgLevel
     *                the level code for the message to log
     * @param msg
     *                the actual message to log
     * @param exception
     *                the actual Exception to log
     */
    public abstract void put(int msgLevel, String msg, Throwable exception);
    
    /**
     * Logs a message and an exception of the given level.
     * 
     * @param msgLevel
     *                the level code for the message to log
     * @param msgId
     *                A locale agnostic form of the message
     * @param params
     *                the Object arguments to plug into the message               
     * @param exception
     *                the actual Exception to log
     */
    public abstract void put(int msgLevel, StringId msgId, Object[] params, Throwable exception);

    static void formatText(PrintWriter writer, String target,
                           int initialLength) {
      BreakIterator boundary = BreakIterator.getLineInstance();
      boundary.setText(target);
      int start = boundary.first();
      int end = boundary.next();
      int lineLength = initialLength;

      while (end != BreakIterator.DONE) {
        // Look at the end and only accept whitespace breaks
        char endChar = target.charAt(end-1);
        while (!Character.isWhitespace(endChar)) {
          int lastEnd = end;
          end = boundary.next();
          if (end == BreakIterator.DONE) {
            // give up. We are at the end of the string
            end = lastEnd;
            break;
          }
          endChar = target.charAt(end-1);
        }
        int wordEnd = end;
        if (endChar == '\n') {
          // trim off the \n since println will do it for us
          wordEnd--;
          if (wordEnd > 0 && target.charAt(wordEnd-1) == '\r') {
            wordEnd--;
          }
        } else if (endChar == '\t') {
          // figure tabs use 8 characters
          lineLength += 7;
        }
      String word = target.substring(start, wordEnd);
      lineLength += word.length();
      writer.print(word);
      if (endChar == '\n' || endChar == '\r') {
        // force end of line
        writer.println();
        writer.print("  ");
        lineLength = 2;
      }
      start = end;
      end = boundary.next();
    }
    if (lineLength != 0) {
      writer.println();
    }
  }

  /**
   * Check if a message of the given level would actually be logged by this
   * logger.
   * This check is based on the Logger effective level.
   * @param level a message logging level
   * @return true if the given message level is currently being logged.
   */
  public boolean isLoggable(int level) {
      return getLogWriterLevel() <= level;
  }

  /**
   * Log a message, with associated Throwable information.
   * If the logger is currently enabled for the given message level then the
   * given message is logged.
   * @param level One of the message level identifiers, e.g. SEVERE
   * @param message The string message
   * @param thrown - Throwable associated with log message.
   */
  public void log(int level, String message, Throwable thrown) {
      if (isLoggable(level)) {
        this.put(level, message, thrown);
      }
  }

  /**
   * Log a message.
   * If the logger is currently enabled for the given message level then the
   * given message is logged.
   * @param level One of the message level identifiers, e.g. SEVERE
   * @param message The string message
   */
  public void log(int level, String message) {
      if (isLoggable(level)) {
        this.put(level, message, null);
      }
  }

  public Handler getHandler() {
    return new GemFireHandler(this);
  }

  /** Utility to get a stack trace as a string from a Throwable */
  public static String getStackTrace(Throwable aThrowable) {
   StringWriter sw = new StringWriter();
   aThrowable.printStackTrace(new PrintWriter(sw, true));
   return sw.toString();
  }
  
  /**
   * Utility to periodically log a stack trace for a thread.  The thread should
   * be alive when this method is invoked.
   * @param toStdout whether to log to stdout or to this log writer
   * @param targetThread the thread to snapshot
   * @param interval millis to wait betweeen snaps
   * @param done when to stop snapshotting (also ceases if targetThread dies)
   */
  public void logTraces(final boolean toStdout,
      final Thread targetThread, final int interval, final AtomicBoolean done) {
    if (targetThread == null) {
      return;
    }
    Thread watcherThread = new Thread("Stack Tracer for '" + targetThread.getName() + "'") {
      @Override
      public void run() {
        while (!done.get()) {
          try { Thread.sleep(500); } catch (InterruptedException e) { return; } 
          if (!done.get() && targetThread.isAlive()) {
            StringBuffer sb = new StringBuffer(500);
            if (toStdout) {
              sb.append("[trace ").append(getTimeStamp()).append("] ");
            }
            StackTraceElement[] els = targetThread.getStackTrace();
            sb.append("Stack trace for '").append(targetThread.toString()).append("'\n");
            if (els.length > 0) {
              for (int i=0; i<els.length; i++) {
                sb.append("\tat ").append(els[i].toString()).append("\n");
              }
            }
            else {
              sb.append("    no stack\n");
            }
            if (toStdout) {
              System.out.println(sb.toString());
            } else {
              info(LocalizedStrings.DEBUG, sb.toString());
            }
          }
        }
      }
    };
    watcherThread.start();
  }

  /** Utility to get a stack trace for a thread */
  public static StringBuffer getStackTrace(Thread targetThread) {
    StringBuffer sb = new StringBuffer(500);
    StackTraceElement[] els = targetThread.getStackTrace();
    sb.append("Stack trace for '").append(targetThread.toString()).append("'\n");
    if (els.length > 0) {
      for (int i=0; i<els.length; i++) {
        sb.append("\tat ").append(els[i].toString()).append("\n");
      }
    }
    else {
      sb.append("    no stack\n");
    }
    return sb;
  }


  // instance variables
  private final DateFormat timeFormatter;

  /*
   * @see org.apache.geode.LogWriter
   * @since GemFire 6.0
   */
  public org.apache.geode.LogWriter convertToLogWriter() {
    return this;
  }
  
  /*
   * @see org.apache.geode.LogWriterI18n
   * @since GemFire 6.0
   */
  public LogWriterI18n convertToLogWriterI18n() {
    return this;
  }

  public static void setStartupListener(StartupStatusListener mainListener) {
    LogWriterImpl.startupListener= mainListener;
  }
  
  public static StartupStatusListener getStartupListener() {
    return LogWriterImpl.startupListener;
  }
}
